
// +---------------------------------------------------------------------------+
// | VERSION 2.1                                                               |
// +---------------------------------------------------------------------------+

package commonlib.domain;

import java.util.ArrayList;




/**
 * Permite obtener las k aproximaciones mas probables mediante unas
 * reglas de transformacion y un diccionario.
 * @autor todo.grupo.5.1
 */

public class CorrectWord
{
    // VARIABLES: --------------------------------------------------------------

    private Rules rule = new Rules();
    private Dictionary dictionary = new Dictionary();
    private String wordS;
    private int numSuggestions = 1;
    private int N = 3;
    private ArrayList<StackContainer> stack;
    private ArrayList<SuggestionContainer> allSuggestions;

    // CONSTRUCTORA: -----------------------------------------------------------

    /**
     * Crea un objeto de tipo CorrectWord
     */
    public CorrectWord() {
        stack = new ArrayList<StackContainer>();
        allSuggestions = new ArrayList<SuggestionContainer>();
    }

    // PUBLIC METHODS: ---------------------------------------------------------

    /**
    * Este metodo devuelve las sug palabras mas parecidas segun las reglas de r.
    *
    * @param r Es el conjunto de reglas
    * @param d Es el diccionario de palabras
    * @param s palabra que se ha de corregir
    * @param n es el valor de tolerancia
    * @param sug el numero de palabras aproximadas que te dara le metodo
    * @return retorna una lista con palabras correctas
    */
    public ArrayList<String> getCorrection(Rules r, Dictionary d, String s, int n, int sug) {
        this.rule = r;
        this.dictionary = d;
        this.wordS = s;
        this.numSuggestions = sug;
        this.N = n;
        System.out.println(s);
        ArrayList<String> suggestionWords = new ArrayList<String>();
        System.out.println("Començo a iterar pel dicionari");
        suggestionWords = iterateDictionary();
        System.out.println(suggestionWords.size() );
        // returns from 0 to the suggestion value index.
        for (int i = numSuggestions; i < suggestionWords.size(); ++i) {
            suggestionWords.remove(i);
        }
        return suggestionWords;
    }

    // PRIVATE MAIN METHODS: ---------------------------------------------------
    private ArrayList<String> iterateDictionary() {
        IteratorTST iterator = dictionary.iterator();

        int hasNext = iterator.hasNext();

        boolean endIteration = false;

        while (!endIteration && hasNext != 0)
        {
            Node node = new Node<NodeData>(new NodeData(wordS.length()));

            // CHECK IF JUMP BRANCH BECAUSE OF THE LOW PROBABILITIES: ----------

            if (stack.size() >= N) {
                int count = 0;
                for (int i = 1; i <= N; i++)
                {
                    if (!stack.get(stack.size() - i).getNodeData().areAnyProbabilities())
                    {
                        ++count;
                    }
                }

                // check if we need to stop moving throught the current branch:
                if (count == N) {
                    System.out.println("BREAK");
                    hasNext = iterator.hasNextBack();

                    if (iterator.hasNextBro())
                    {
                        hasNext = 1;
                        node = iterator.nextBro();
                    }
                    else if (hasNext == 0)
                    {
                        endIteration = true;
                        node = iterator.next();

                    }
                    else
                    {
                        node = iterator.nextBack();
                    }
                }
                else
                {
                    node = iterator.next();
                }
            }
            else
            {
                node = iterator.next();
            }

            // ADD TO STACK / REMOVE FROM STACK: -------------------------------

            if (hasNext < 0)
            {
                int foo = stack.size() + hasNext - 1;

                stack.subList(foo, stack.size()).clear();

                NodeData nodeData = new NodeData(wordS.length());
                node.setData(nodeData);
                stack.add(new StackContainer(node, 0));

            }
            else
            {
                node.setData(new NodeData(wordS.length()));

                int levelNode = node.getLevel();
                int levelTop = 0;
                if (stack.isEmpty()) {
                    levelTop = levelNode + 1;
                }
                else
                {
                    levelTop = stack.get(stack.size() - 1).getNode().getLevel();
                }

                if (levelNode == levelTop)
                {
                    // next node is a right brother so we need to pop out the
                    // latest node of the stack:
                    stack.remove(stack.size() - 1);
                }
                stack.add(new StackContainer(node, 0));
            }

            String wordW = getWordFromStack(stack.size());

            SuggestionContainer suggestion = new SuggestionContainer();
            suggestion = getSuggestion(wordW);


            // if it's a terminal node:
            if (node.getMarked()) {
                // add it to the suggestion list:
                this.allSuggestions.add(suggestion);
                System.out.println("Evaluated: "+wordW);
            }

            hasNext = iterator.hasNext();
        }

        // sort by highest probability and split for the first best
        // numSuggestions
        return getSortedSuggestions();
    }

    private SuggestionContainer getSuggestion(String wordW)
    {
        // APPLICATION ALGORITHM: ----------------------------------------------

        int i = stack.size() - 1;

        String alpha = wordW;

        for (int j = 0; j < wordS.length(); j++) {
            String beta = new String();
            for (int k = 0; k <= j; k++) {
                beta += Character.toString(wordS.charAt(k));
            }

            // MAIN RULE:
            // while alpha and beta are smaller than the N, we search for
            // the probability P( alpha -> beta ):
            if (alpha.length() <= N && beta.length() <= N) {
                ABPair pair = new ABPair(alpha, beta);
                double prob;
                if (alpha.equals(beta)) {
                    prob = 1;
                } else {
                    prob = rule.getRuleProb(pair);
                }
                if (prob != 0.0) {
                    stack.get(i).addProbability(prob, 1, j);
                }
            }

            // SECONDARY RULE:
            // get substrings of alpha and beta and beta and look back to the
            // already found probabilities:
            // x : iterates alpha
            // y : iterates beta
            for (int x = 1; x <= N && x < alpha.length(); x++) {
                String subAlpha = alpha.substring(alpha.length() - x);
                String matrixAlpha = "";
                for (int k = 0; k < alpha.length() - x; k++) {
                    matrixAlpha += Character.toString(alpha.charAt(k));
                }

                for (int y = 1; y <= N && y < beta.length(); y++) {
                    String subBeta = beta.substring(beta.length() - y);
                    String matrixBeta = "";
                    for (int k = 0; k < beta.length() - y; k++) {
                        matrixBeta += Character.toString(beta.charAt(k));
                    }

                    ABPair subPair = new ABPair(subAlpha, subBeta);
                    double probSub;
                    if (subAlpha.equals(subBeta))
                    {
                        probSub = 1;
                    }
                    else
                    {
                        probSub = rule.getRuleProb(subPair);
                    }

                    // We have to look into the generated matrix for the
                    // probabilities at the matrixAlpha.length() and
                    // matrixBeta.length() positions. Later on is check
                    // weather those probabilities are relevant or not and added
                    // to the node plus subProb:

                    ArrayList<ChanceCombination> matrixProbs;
                    matrixProbs = new ArrayList<ChanceCombination>();

                    int posA = i - subAlpha.length();
                    int posB = j - subBeta.length();

                    if (probSub != 0.0 && posA >= 0 && posB >= 0) {
                        matrixProbs = stack.get(posA).getProbabilities(posB);

                        for (ChanceCombination matrixProb : matrixProbs)
                        {
                            double prob = matrixProb.getProbability();
                            prob += probSub;
                            int nRules = matrixProb.getNumRulesApplied();
                            ++nRules;
                            stack.get(i).addProbability(prob, nRules, j);
                        }
                    }
                }
            }
        }

        // calculate max probability for the word:
        double prob = getHighestProbability(wordW);
        SuggestionContainer suggestion = new SuggestionContainer(wordW, prob);
        return suggestion;
    }

    ////////////////////////////////////////////////////////////////////////////
    // PRIVATE METHODS: --------------------------------------------------------

    private String revertString(String original) {
        String reverse = new StringBuffer(original).reverse().toString();
        return reverse;
    }

    private String getWordFromStack(int length) {
        String word = new String();

        for (int i = 0; i < length; i++) {
            word += stack.get(i).getNode().getLetter();
        }

        return word;
    }

    private double getHighestProbability(String wordW) {
        int i = wordW.length() - 1;
        int j = wordS.length() - 1;
        double maxProb = 0.0;

        if (!stack.get(i).isEmpty(j)) {
            ArrayList<ChanceCombination> probs = stack.get(i).getProbabilities(j);

            for (ChanceCombination elem : probs) {
                double prob = elem.getRealProbability();
                maxProb = (prob > maxProb) ? prob : maxProb;
            }
        }

        return maxProb;
    }

    private ArrayList<String> getSortedSuggestions()
    {
        ArrayList<String> suggestions = new ArrayList<String>();

        if (!allSuggestions.isEmpty())
        {

            quicksort(allSuggestions, 0, allSuggestions.size()-1);

            System.out.println("-------------- FINAL RESULT -----------------");
            for (int i = 0; i < allSuggestions.size() ; i++)
            {
                System.out.print(allSuggestions.get(i).getWord());
                System.out.println(" - "+allSuggestions.get(i).getProbability());
            }

            int size = allSuggestions.size();
            for (int i = size-1; i >= 0 && i >= size - numSuggestions; i--)
            {
                suggestions.add(allSuggestions.get(i).getWord());
            }
        }
        return suggestions;
    }


    private void quicksort(ArrayList<SuggestionContainer> vector, int s, int e)
    {
        int i = s;
        int j = e;
        SuggestionContainer pivot = vector.get((s + e)/2);
        SuggestionContainer foo = new SuggestionContainer();

        do
        {
            while(vector.get(i).getProbability() < pivot.getProbability()) i++;
            while(vector.get(j).getProbability() > pivot.getProbability()) j--;

            if (i <= j)
            {
                String jWord = vector.get(j).getWord();
                double jProb = vector.get(j).getProbability();
                vector.get(j).setWord( vector.get(i).getWord() );
                vector.get(j).setProbability( vector.get(i).getProbability() );

                vector.get(i).setWord(jWord);
                vector.get(i).setProbability(jProb);
                i++;
                j--;
            }

        } while (i <= j);

        if (s < j) quicksort(vector, s, j);
        if (e > i) quicksort(vector, i, e);
    }

    ////////////////////////////////////////////////////////////////////////////
    // +-----------------------------------------------------------------------+
    // | INNER CLASS : StackContainer                                          |
    // +-----------------------------------------------------------------------+

    private class StackContainer
    {
        // VARIABLES: ----------------------------------------------------------

        Node<NodeData> node;
        int steps;

        // CONSTRUCTOR: --------------------------------------------------------
        public StackContainer()
        {
            this.node = new Node<NodeData>(new NodeData(wordS.length()));
        }

        public StackContainer(Node node, int steps)
        {
            this.node = node;
            this.steps = steps;
        }

        // GETTERS: ------------------------------------------------------------
        public Node getNode()
        {
            return this.node;
        }

        public int getSteps() {
            return this.steps;
        }

        public NodeData getNodeData() {
            return (NodeData) this.node.getData();
        }

        // SETTERS: ------------------------------------------------------------
        public void setNode(Node node) {
            this.node = node;
        }

        public void setSteps(int steps) {
            this.steps = steps;
        }

        public void setNodeData(NodeData nodeData) {
            this.node.setData(nodeData);
        }

        // OTHER METHODS: ------------------------------------------------------
        public void addProbability(double prob, int numRulesApplied, int j) {
            NodeData nodeData = (NodeData) this.node.getData();
            nodeData.addProbability(prob, numRulesApplied, j);
            this.node.setData(nodeData);
        }

        public void setProbability(double prob, int numRulesApplied, int j) {
            NodeData nodeData = (NodeData) this.node.getData();
            nodeData.setProbability(prob, numRulesApplied, j);
            this.node.setData(nodeData);
        }

        public ArrayList<ChanceCombination> getProbabilities(int j) {

            return this.node.getData().getProbabilities(j);
        }

        public boolean isEmpty(int j) {
            boolean foo = this.node.getData().isEmpty(j);
            return foo;
        }
    }

    // +-----------------------------------------------------------------------+
    // | INNER CLASS : SuggestionContainer                                     |
    // +-----------------------------------------------------------------------+

    private class SuggestionContainer
    {
        // VARIABLES: ----------------------------------------------------------

        String wordString;
        double probability;

        // CONSTRUCTOR: --------------------------------------------------------
        public SuggestionContainer() {
            this.wordString = "";
            this.probability = 0.0;
        }

        public SuggestionContainer(String wordString, double probability) {
            this.wordString = wordString;
            this.probability = probability;
        }

        // GETTERS: ------------------------------------------------------------
        public String getWord() {
            return this.wordString;
        }

        public double getProbability() {
            return this.probability;
        }

        // SETTERS: ------------------------------------------------------------
        public void setWord(String wordString) {
            this.wordString = wordString;
        }

        public void setProbability(double probability) {
            this.probability = probability;
        }
    }
}